package com.bajiao.common.util;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * <P>
 * 用途:redis 锁工具类
 * </p>
 *
 * @author: FanJq
 * @create: 2024-10-10
 **/
@Component
public class DistributeLockUtil {
    /**
     * 引入redisTemplate
     */
    @Autowired
    private RedisTemplate redisTemplate;
    /**
     * 超时时间，10s
     */
    private static final long HANDLE_MSECS = 10000L;
    /**
     * 重复获取锁的时间间隔 50毫秒
     */
    private static final long TRY_INTERVAL = 50L;
    /**
     * 重复获取锁的超时时间 10s
     */
    private static final long TIME_OUT_MSECS = 10000L;

    /**
     * 获取锁
     *
     * @param lockName 锁的名称
     * @return 释放锁的时间的时间戳
     * @throws InterruptedException 中断异常
     */
    public Long getLock(String lockName) {

        long waitTime = System.currentTimeMillis() + TIME_OUT_MSECS;
        long lockTimeout;
        Object obj = redisTemplate.opsForValue().get(lockName);
        long expire = obj == null ? 0L : (Long) obj;

        // 时间段内，循环获取锁
        while (System.currentTimeMillis() < waitTime) {
            lockTimeout = System.currentTimeMillis() + HANDLE_MSECS + 1L;

            // 获取到锁后，设置过期时间
            if (redisTemplate.opsForValue().setIfAbsent(lockName, lockTimeout)) {
                redisTemplate.expire(lockName, HANDLE_MSECS, TimeUnit.MILLISECONDS);
                return lockTimeout;

                // 发现死锁，重置锁的过期时间
            } else if (expire < System.currentTimeMillis()) {
                redisTemplate.opsForValue().set(lockName, lockTimeout, HANDLE_MSECS, TimeUnit.MILLISECONDS);

                // 重置后，操作时间小于获取时间，允许等待操作时间
                // 操作时间大于获取时间，肯定获取不到，直接结束；
                if (HANDLE_MSECS > TIME_OUT_MSECS) {
                    return 0L;
                }
            }

            try {
                Thread.sleep(TRY_INTERVAL);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        return 0L;
    }

    /**
     * 释放锁的情况：
     *
     * 1.业务执行过程中，发生异常必须释放锁；（调用释放方法）
     * 2.业务执行完成，必须释放锁；（调用释放方法）
     * 3.发生死锁，必须释放锁；（超时设置）
     * 4.异步处理导致主程序过早释放锁；（取消finally，所有代码跑完才释放锁）
     */

    /**
     * 同步释放锁
     *
     * @param lockName    锁的名称
     * @param lockTimeout 超时时间
     */
    public void releaseLock(String lockName, long lockTimeout) {

        // 获取锁的释放时间， 如果锁的释放时间和定义的释放时间一致，那么释放锁
        Object lockTime = redisTemplate.opsForValue().get(lockName);
        long currentLockTime = lockTime == null ? 0L : (long) lockTime;

        if (lockTimeout != 0L && currentLockTime == lockTimeout) {
            redisTemplate.opsForValue().getOperations().delete(lockName);
        }
    }

    /**
     * 异步释放锁
     *
     * @param lockName 锁的名称
     */
    public void releaseLock(String lockName) {

        redisTemplate.opsForValue().getOperations().delete(lockName);
    }

}
